using System;
using System.Collections.Generic;   // provides List<T> and Comparer<T>
using System.Reflection;            // provides PropertyInfo
using System.Runtime.Serialization; // provides SerializationInfo for our custom Exception

namespace MpSoft.Library.CommonLibrary
{
    public class SortTDirection
    {
        public enum Direction
        {
            Ascending = 0,
            Descending = 1
        }
    }

#region Custom Exceptions
    public class PropertyNotFoundException : Exception
    {
        public PropertyNotFoundException()
            : base() { }
        public PropertyNotFoundException(string message)
            : base(message) { }
        public PropertyNotFoundException(string message, Exception innerException)
            : base(message, innerException) { }
        public PropertyNotFoundException(SerializationInfo info, StreamingContext context)
            : base(info, context) { }
    }
#endregion

    public class TComparer<T> : Comparer<T>
    {
        private Type _obj;
        private PropertyInfo _property;
        private SortTDirection.Direction _direction;

        public TComparer()
        {
            _obj = typeof(T); //we don't care what T is

            // Get T's properties
            PropertyInfo[] properties = _obj.GetProperties();
            if (properties.Length > 0)
                _property = properties[0];
            else
                throw new InvalidOperationException(String.Format(
                    "{0} does not have any property", _obj.Name));

            // Set default direction
            _direction = SortTDirection.Direction.Ascending;
        }
        public TComparer(PropertyInfo prop, SortTDirection.Direction direction)
            : this()
        {
            // Set property
            if (prop != null)
                _property = prop;
            else
                throw new PropertyNotFoundException(
                    String.Format("The specified PropertyInfo does not belong to {0}",
                                    _obj.Name));
            // Set direction
            _direction = direction;
        }
        public TComparer(string strPropertyName, SortTDirection.Direction direction)
            : this()
        {
            // Set property
            PropertyInfo p = _obj.GetProperty(strPropertyName);
            if (p != null)
                _property = p;
            else
                throw new PropertyNotFoundException(
                    String.Format("Property {0} does not belong to {1}",
                                    strPropertyName, _obj.Name));

            // Set direction
            _direction = direction;
        }

#region Comparer<T> implementations
        public override int Compare(T a, T b)
        {
            int compareValue = 0;

            object valA = _property.GetValue(a, null); // Get the value of property _property in 'a'
            object valB = _property.GetValue(b, null); // Get the value of property _property in 'b'

            if (_property.PropertyType.Equals(typeof(DateTime)))
                // DateTime objects must be compared using DateTime.Compare
                compareValue = DateTime.Compare((DateTime)valA, (DateTime)valB);
            else
                // The rest using String.Compare
                compareValue = String.Compare(valA.ToString(), valB.ToString());

            // Reverse order if sort direction is Descending
            if (this._direction == SortTDirection.Direction.Descending)
                compareValue *= -1;

            return compareValue;
        }
#endregion

#region Static helpers
        public static TComparer<T> SortBy(string strPropertyName, SortTDirection.Direction direction)
        {
            return new TComparer<T>(strPropertyName, direction);
        }
        public static TComparer<T> SortBy(PropertyInfo prop, SortTDirection.Direction direction)
        {
            return new TComparer<T>(prop, direction);
        }
#endregion
    }
}
